package com.hzw.saas.service.product.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.annotation.Resource;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzw.saas.api.product.IBalanceOrderService;
import com.hzw.saas.api.product.IPaymentOfflineService;
import com.hzw.saas.api.product.IPaymentOnlineService;
import com.hzw.saas.api.product.IUserBalanceService;
import com.hzw.saas.api.product.dto.BalanceOrderDto;
import com.hzw.saas.api.product.dto.BalanceOrderInfoDto;
import com.hzw.saas.api.product.dto.PaymentOfflineDto;
import com.hzw.saas.api.product.dto.PaymentOnlineDto;
import com.hzw.saas.api.product.enums.OrderStatus;
import com.hzw.saas.api.product.enums.OrderType;
import com.hzw.saas.api.product.enums.PayMode;
import com.hzw.saas.common.config.annotation.RedisProcessLock;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.config.util.PageParam;
import com.hzw.saas.common.config.util.PageUtils;
import com.hzw.saas.common.util.IdWorker;
import com.hzw.saas.service.product.mapper.BalanceOrderMapper;
import com.hzw.saas.service.product.model.BalanceOrder;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;

/**
 * @author zzl
 * @since 01/28/2021
 */
@Slf4j
@Service("balanceOrderServiceImpl")
@RequiredArgsConstructor
public class BalanceOrderServiceImpl extends ServiceImpl<BalanceOrderMapper, BalanceOrder> implements IBalanceOrderService, IService<BalanceOrder> {

    private final IdWorker idWorker;
    private final MapperFacade mapperFacade;
    @Resource(name="paymentOnlineServiceImpl")
    private IPaymentOnlineService paymentOnlineService;
    @Resource(name="paymentOfflineServiceImpl")
    private IPaymentOfflineService paymentOfflineService;
    @Resource(name = "balanceOrderServiceImpl")
    private IBalanceOrderService balanceOrderService;
    @Resource(name = "userBalanceServiceImpl")
    private IUserBalanceService userBalanceService;

    @Override
    public BalanceOrderDto get(String orderId) {
        if (StringUtils.isBlank(orderId)) {
            return null;
        }
        BalanceOrder balanceOrder = this.getById(orderId);
        return mapperFacade.map(balanceOrder, BalanceOrderDto.class);
    }

    @Override
    public BalanceOrderInfoDto getInfo(String orderId) {
        if (StringUtils.isBlank(orderId)) {
            return null;
        }
        BalanceOrderDto balanceOrderDto = this.get(orderId);
        if (balanceOrderDto == null) {
            return null;
        }
        return getInfo(balanceOrderDto);
    }

    @Override
    public IPage<BalanceOrderDto> list(PageParam pageParam, BalanceOrderDto cnd) {
        BalanceOrder entity = mapperFacade.map(cnd, BalanceOrder.class);
        IPage<BalanceOrder> page = this.page(pageParam.convert(), Wrappers.<BalanceOrder>query().setEntity(entity));
        return PageUtils.convert(page, BalanceOrderDto.class);
    }

    @Override
    public IPage<BalanceOrderInfoDto> listInfo(PageParam pageParam, BalanceOrderDto cnd) {
        List<BalanceOrderInfoDto> orderInfoDtoList = new ArrayList<>();
        IPage<BalanceOrderDto> list = this.list(pageParam, cnd);
        for (BalanceOrderDto balanceOrderDto : list.getRecords()) {
            BalanceOrderInfoDto balanceOrderInfoDto = this.getInfo(balanceOrderDto);
            orderInfoDtoList.add(balanceOrderInfoDto);
        }
        IPage<BalanceOrderInfoDto> result = new Page<>(list.getCurrent(), list.getSize(), list.getTotal());
        result.setRecords(orderInfoDtoList);
        return result;
    }

    @Override
    public BalanceOrderDto add(BalanceOrderDto balanceOrderDto) {
        if (StringUtils.isBlank(balanceOrderDto.getOrderId())) {
            balanceOrderDto.setOrderId(String.valueOf(idWorker.nextId()));
        }
        if (balanceOrderDto.getCreateTime() == null) {
            balanceOrderDto.setCreateTime(new Date());
        }

        BalanceOrder balanceOrder = mapperFacade.map(balanceOrderDto, BalanceOrder.class);
        this.save(balanceOrder);
        return balanceOrderDto;
    }

    @Override
    public boolean update(BalanceOrderDto balanceOrderDto) {
        if (StringUtils.isBlank(balanceOrderDto.getOrderId())) {
            throw DefaultSaasException.build("order id is blank").status(HttpStatus.BAD_REQUEST);
        }
        return this.updateById(mapperFacade.map(balanceOrderDto, BalanceOrder.class));
    }

    private BalanceOrderInfoDto getInfo(BalanceOrderDto balanceOrderDto) {
        BalanceOrderInfoDto balanceOrderInfoDto = new BalanceOrderInfoDto();
        balanceOrderInfoDto.setOrder(balanceOrderDto);
        if (Objects.equals(balanceOrderDto.getPayMode(), PayMode.ONLINE_PAY.value())) {
            PaymentOnlineDto paymentOnlineDto = paymentOnlineService.getByOrder(balanceOrderDto.getOrderId());
            balanceOrderInfoDto.setPaymentOnline(paymentOnlineDto);
        } else if (Objects.equals(balanceOrderDto.getPayMode(), PayMode.OFFLINE_PAY.value())) {
            PaymentOfflineDto paymentOfflineDto = paymentOfflineService.getByOrder(balanceOrderDto.getOrderId());
            balanceOrderInfoDto.setPaymentOffline(paymentOfflineDto);
        }
        return balanceOrderInfoDto;
    }

    @Override
    public BalanceOrderDto createOfflineBalanceOrder(BalanceOrderDto balanceOrderDto) {
        balanceOrderDto.setCreateTime(new Date())
            .setPayTime(new Date()).setFinallyTime(new Date())
            .setPayMode(PayMode.OFFLINE_PAY.value())
            .setIsPayed(1).setStatus(OrderStatus.PAY_SUCCESS.value());
        balanceOrderDto = balanceOrderService.add(balanceOrderDto);
        /** 添加支付信息*/
        addPaymentOffLineInfo(balanceOrderDto.getOrderId(), balanceOrderDto.getUserId(), OrderType.BALANCE, balanceOrderDto.getPayValue());
        return balanceOrderDto;
    }

    @Override
    @Transactional
    @RedisProcessLock(lockName = "BalanceOrderConfirm", key = "#orderId")
    public void balanceOrderConfirm(String orderId, String userId) {
        BalanceOrderDto balanceOrderDto = balanceOrderService.get(orderId);
        AssertUtil.assertThrow("确认订单失败，订单不存在", Objects.isNull(balanceOrderDto));
        OrderStatus orderStatus = OrderStatus.instance(balanceOrderDto.getStatus());

        /** 重复确认*/
        if(OrderStatus.CONFIRM_SUCCESS == orderStatus) {
            return;
        }

        /** 不是支付完成状态或者不是确认失败*/
        if(OrderStatus.PAY_SUCCESS != orderStatus && OrderStatus.CONFIRM_FAIL != orderStatus) {
            log.error("确认订单{}失败，订单状态异常(:{})", orderId, orderStatus.text());
            AssertUtil.assertThrow(String.format("确认订单失败，订单状态异常(:%d)", orderStatus.value()), true);
        }

        /** 更新余额*/
        userBalanceService.addBalance(balanceOrderDto.getUserId(), balanceOrderDto.getPayValue());

        /** 最后更新订单状态*/
        BalanceOrderDto balanceOrderQuery = new BalanceOrderDto();
        AssertUtil.assertThrow("确认订单失败，支付信息丢失", Objects.isNull(balanceOrderQuery));
        balanceOrderQuery.setOrderId(balanceOrderDto.getOrderId());
        balanceOrderQuery.setStatus(OrderStatus.CONFIRM_SUCCESS.value());
        balanceOrderService.update(balanceOrderQuery);

        /** 更新支付表*/
        PaymentOfflineDto paymentOfflineDto = paymentOfflineService.getByOrder(orderId);
        AssertUtil.assertThrow("确认订单失败，支付信息丢失", Objects.isNull(paymentOfflineDto));
        paymentOfflineDto.setOrderId(orderId);
        paymentOfflineDto.setAdminId(userId);
        paymentOfflineDto.setPayStatus(OrderStatus.CONFIRM_SUCCESS.value());
        paymentOfflineService.update(paymentOfflineDto);
    }

    private void addPaymentOffLineInfo(String orderId, String userId, OrderType orderType, Double payAmount) {
        PaymentOfflineDto paymentOfflineQuery = new PaymentOfflineDto();
        paymentOfflineQuery.setOrderId(orderId);
        paymentOfflineQuery.setPayId(String.valueOf(idWorker.nextId()));
        paymentOfflineQuery.setCreateTime(new Date());
        paymentOfflineQuery.setPayStatus(-1);
        paymentOfflineQuery.setPayStatus(orderType.value());
        if(payAmount == null) {
            paymentOfflineQuery.setPayAmount(-1d);
        } else {
            paymentOfflineQuery.setPayAmount(payAmount);
        }
        paymentOfflineQuery.setUserId(userId);
        paymentOfflineQuery.setFlowId("-1");
        paymentOfflineQuery.setPayStatus(OrderStatus.PAY_SUCCESS.value());
        paymentOfflineQuery.setPayTime(new Date());
        paymentOfflineQuery.setOrderType(orderType.value());
        paymentOfflineService.add(paymentOfflineQuery);
    }

}
